package com.buddy.qreport.common.mybatis.interceptor;


import com.buddy.qreport.common.mybatis.entity.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.scripting.defaults.RawSqlSource;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.*;

/**
 * mybatis 分页拦截器
 * 针对mysql数据的分页，别的数据库 自己实现去
 */

@Intercepts({

        @Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),

})

@Component
@Slf4j
public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        Executor executor = (Executor) invocation.getTarget();

        Object[] args = invocation.getArgs();

        MappedStatement mappedStatement = (MappedStatement) args[0];

        RowBounds rowBounds = (RowBounds) args[2];

        ResultHandler resultHandler = (ResultHandler) args[3];

        Object queryParams = args[1];

        BoundSql boundSql = null;

        Page page = null;

        if(args.length == 4){

            //根据当前查询参数 获取动态sql
            boundSql = mappedStatement.getBoundSql(queryParams);

        }else{
            //当前sql为静态sql
            boundSql = (BoundSql) args[5];
        }


        //判断当前查询参数中是否包含Page对象，如果包含则进行分页


        if(queryParams instanceof Page){

            page = (Page) queryParams;

        }else if(queryParams instanceof Map){


            Set<Map.Entry> paramsSet = ((Map) queryParams).entrySet();

            for(Map.Entry entry :paramsSet){

                if(entry.getValue() instanceof Page){

                    page = (Page) entry.getValue();

                    break;
                }
            }


        }


        if(page != null){

            //先进行总数查询

            String srcSql = boundSql.getSql();

            //生成查询总条数sql
            String countSql = "select count(*) from (" + srcSql + ") as t_" + UUID.randomUUID().toString().replaceAll("-","");

            //返回的MappedStatement对象已经处理过sqlSource了 所以可以直接使用Executor进行查询了
            MappedStatement countStatement = createCountStatement(mappedStatement,countSql,queryParams.getClass());

            //查询参数使用当前拦截的查询方法即可，ResultHandler在查询总数的时候 不适用，设为null
            List<Long> result = executor.query(countStatement,queryParams,rowBounds,null);

            //完成了总数查询
            page.setTotal(result.get(0));


            //下面进行分页查询



            int offset = ((page.getPage() - 1) * page.getSize());

            String limitSql = srcSql + " limit " + offset + "," + page.getSize();



            //这里用反射或者new新对象都可以 ，我懒，就用反射
            Field field = BoundSql.class.getDeclaredField("sql");

            field.setAccessible(true);

            field.set(boundSql,limitSql);

            CacheKey cacheKey = executor.createCacheKey(mappedStatement,queryParams,rowBounds,boundSql);

            //执行查询，这里调用executor.query不会被拦截，因为这个是代理源对象
            List queryResult = executor.query(mappedStatement,queryParams,rowBounds,resultHandler,cacheKey,boundSql);

            page.setRows(queryResult);

            log.debug("已处理分页查询，sql={}",srcSql);

            return queryResult;

        }else {

            return invocation.proceed();
        }


    }



    private MappedStatement createCountStatement(MappedStatement src,String countSql,Class paramsType){



        ResultMap resultMap = new ResultMap.Builder(src.getConfiguration(), UUID.randomUUID().toString(),Long.TYPE, Collections.EMPTY_LIST).build();


        //由于进行总数查询的时候呢  所有sql都已经解析完毕 所以这里使用RawSqlSource 使其成为一条静态sql，顺便处理一下占位符

        RawSqlSource sqlSource = new RawSqlSource(src.getConfiguration(),countSql,paramsType);


        return new MappedStatement.Builder(src.getConfiguration(),UUID.randomUUID().toString(),sqlSource, SqlCommandType.SELECT)

                .resultMaps(Collections.singletonList(resultMap))
                .resource(src.getResource())
                .timeout(src.getTimeout())
                .build();

    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
